home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / uucon < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  67.0 KB  |  1,936 lines

  1. #!/usr/local/bin/gawk -f
  2. #!/usr/bin/awk -f
  3. # @(#) uucon.gawk 2.1 97/01/08
  4. # 91/04/19 john h. dubois iii (john@armory.com)
  5. # 91/06/30 Replaced * with + in split pattern (awk didn't care but gawk does)
  6. # 91/08/04 Added separate NOT_POLLED_WARN parameter
  7. # 91/08/05 Made awk script exit if no files found; added debug option
  8. # 91/12/31 Moved finds inside of awk program; made awk program a separate file
  9. #          since this change made it larger than a command line can be
  10. # 92/03/03 Removed hardcoding of path to awk so it will work under UNIX
  11. # 92/03/06 Fixed error initializing last-login file
  12. #          Don't exit if no files found if debugging is on
  13. #          Don't use first field of last-login line
  14. # 92/03/10 Changed "startup" message searched for so that it will work with
  15. #          both XENIX and UNIX
  16. # 92/05/02 Fixed check for files waiting for polling systems
  17. #          Changed to be a #!awk script
  18. # 92/12/06 Print warnings if poll file or systems file can't be opened,
  19. #          make sure track files are readable by all
  20. # 92/12/17 Added -n and -l options
  21. # 93/01/25 Changed from using last success startup line to using last
  22. #          successful conversation line;
  23. #          correctly note lack of succesful conversation line with -n & -l
  24. # 93/01/28 Cleaned up/bugfixes
  25. # 93/05/28 Added sorting by last connect time.  Doesn't work across year
  26. #          ends because logfiles don't contain year.
  27. # 93/11/10 Force WePoll[] to be an array
  28. # 93/11/15 Force LastConv[] to be an array
  29. # 94/03/09 Use gawk so - options can be given
  30. # 94/03/12 Close lots of files & commands.  Avoid cding to nonexistant dirs.
  31. # 96/07/04 Cleaned up option processing.  Added andcpurslL options.
  32. #          Let system names be given on command line.  More error checking.
  33. #          Make dates from earlier years sort correctly.  Added rcfile.
  34. #          Truncate system names to 7 chars, because uucico does when creating
  35. #          logfiles.
  36. # 96/07/07 Print incoming UUCP/TCP times in local time.  Added t option.
  37. # 96/11/05 Exit if can't read Systems
  38. # 97/01/08 Made -l and -L sorting work correctly.
  39.  
  40. BEGIN {
  41.     Name = "uucon"
  42.     LogDir = "/usr/spool/uucp/.Log/uucico"
  43.     LCDir = LogDir
  44.     Usage = \
  45. "Usage: " Name " [-ahlLnurst] [-x<debug-leve>] [-c<no-con-warning-period>]\n"\
  46.     "       [-p<not-polled-warning-period>] [-d<lastcon-dir>] [system ...]"
  47.     rcFiles = "/etc/default/uucon"
  48.     ARGC = Opts(Name,Usage,"ac>p>d:rstlLuhx<",0,rcFiles,
  49.     "ALL,NO_CON_WARN,NOT_POLLED_WARN,LC_DIR,RSORT,SITESORT,NOCONVERT",0,"n")
  50.  
  51.     debug = Options["x"]
  52.     if (debug)
  53.     print "debugging is on."
  54.     if ("h" in Options) {
  55.     help(Name,Usage,LCDir,rcFiles)
  56.     exit(0)
  57.     }
  58.  
  59.     if ("d" in Options)
  60.     LCDir = Options["d"]
  61.     toLocal = !("t" in Options)
  62.  
  63.     if (SitesGiven = (ARGC > 1)) {
  64.     for (i = 1; i < ARGC; i++)
  65.         Systems[ARGV[i]]
  66.     if (debug)
  67.         printf "%d sitename(s) given.\n",ARGC-1 > "/dev/stderr"
  68.     }
  69.     All = "a" in Options
  70.     if (Options["L"] || Options["l"]) {    # Print last-conversation lines
  71.     Cmd = "uuname"
  72.     while ((Cmd | getline System) == 1)
  73.         allSystems[substr(System,1,7)]
  74.     close(Cmd)
  75.     CheckSysList(Systems,allSystems,SitesGiven,Name)
  76.     # Report last conv with all systems
  77.     DoLast(Options["l"],LogDir,LCDir,Systems,"r" in Options,
  78.     "s" in Options,allSystems,All)
  79.     exit(0)
  80.     }
  81.  
  82.     # Read poll file and Systems file
  83.     split("",WePoll,"")        # make awk realize this is an array
  84.     if (!Setup("/usr/lib/uucp/Poll.hour",allSystems,WeCall,Never,WePoll))
  85.     exit 1
  86.     CheckSysList(Systems,allSystems,SitesGiven,Name)
  87.  
  88.     # Get/save last-conv lines
  89.     split("",LastConv,"")            # convince awk this is an array
  90.     FindLastConv(LogDir,Systems,LastConv)    # read uucico logs
  91.     if ("u" in Options)
  92.     SaveLastConv(LCDir,LastConv)        # write last-conv files
  93.     ReadLastConv(LCDir,Systems,LastConv,allSystems)    # read last-conv files
  94.     if (All)
  95.     CopySet(Systems,LastConv)
  96.  
  97.     # Set NoConSystems and WarnSystems according to dates on last-conv files
  98.     # Convince awk that these are arrays
  99.     split("",NoConSystems)
  100.     split("",NotPolledSystems)
  101.  
  102.     if ("c" in Options)
  103.     NoConWarn = Options["c"]
  104.     else
  105.     NoConWarn = 1
  106.     if ("p" in Options)
  107.     NotPolledWarn = Options["p"]
  108.     else
  109.     NotPolledWarn = NoConWarn
  110.     FindOldFiles(NoConSystems,NotPolledSystems,NoConWarn,NotPolledWarn)
  111.     ReportNoConv(LastConv,NoConSystems,NotPolledSystems,WeCall,WePoll)
  112. }
  113.  
  114. # Complain about any systems in Systems[] that are not also in allSystems[].
  115. # If no systems given, copy allSystems[] to Systems[].
  116. function CheckSysList(Systems,allSystems,SitesGiven,Name,  sys) {
  117.     if (SitesGiven) {
  118.     for (sys in Systems)
  119.         if (!(sys in allSystems))
  120.         printf \
  121.         "%s: system \"%s\" does not exist in the Systems file.\n",
  122.         Name,sys > "/dev/stderr"
  123.     }
  124.     else
  125.     CopyArr(allSystems,Systems)
  126.     if (debug) {
  127.     print "Final systems list:" > "/dev/stderr"
  128.     for (sys in Systems)
  129.         print sys > "/dev/stderr"
  130.     print "" > "/dev/stderr"
  131.     }
  132. }
  133.  
  134. # Report systems that there has been no successful conversation with for
  135. # more than the configured number of days.
  136. # A warning is printed only for systems that: are polled and are in
  137. # NoConSystems; are called on demand and have jobs waiting and are in
  138. # NoConSystems; or poll us and have jobs waiting and are in NotPolledSystems.
  139. # A system is in:
  140. # WeCall if it has a Systems line that has a call time other than Never.
  141. # WePoll if it is a system we poll.
  142. # NoConSystems if it has a last-conv file at least NoConWarn days old.
  143. # NotPolledSystems if it has a last-conv file at least NotPolledWarn days old.
  144. #
  145. # LastConv contains the last-conv line for each system, for use in warnings.
  146. # It contains an entry for every system in the UUCP configuration.
  147. function ReportNoConv(LastConv,NoConSystems,NotPolledSystems,WeCall,WePoll,
  148. NumFiles,System,Elem,Cmd,When,Warn,SysList,Line,SysType) {
  149.     # Make SysType array so awk will not complain later if nothing is put in it
  150.     split("",SysType)    
  151.     for (System in LastConv) {
  152.     if ((System in NoConSystems) && (System in WePoll))
  153.         SysType[System] = "polled "
  154.     else if ((System in NoConSystems) && (System in WeCall))
  155.         SysType[System] = "called "
  156.     else if ((System in NotPolledSystems) && !(System in WeCall))
  157.         SysType[System] = "polling "
  158.     else if (debug) {
  159.         printf "Systems %s: no warning.\n",System
  160.         continue
  161.     }
  162.     if (debug)
  163.         printf "Systems %s is %s\n",System,SysType[System]
  164.     }
  165.     for (System in SysType)
  166.     SysList = SysList System " "
  167.     if (SysList == "")
  168.     return
  169.     # Check for waiting files.
  170.     Cmd = "for system in " SysList "; do dir=/usr/spool/uucp/$system;"\
  171.     "[ ! -x $dir ] && continue; cd $dir; set -- *;"\
  172.     "[ $1 != '*' ] && echo $system $#; done"
  173.     while ((Cmd | getline Line) == 1) {
  174.     split(Line,Elem)
  175.     NumFiles[Elem[1]] = Elem[2]
  176.     }
  177.     close(Cmd)
  178.     for (System in SysType) {
  179.     if (!(System in WePoll) && !NumFiles[System])
  180.         continue
  181.     split(LastConv[System],Elem)
  182.     if (Elem[3] == "NEVER")
  183.         When = "EVER"
  184.     else
  185.         When = "since " substr(Elem[3],2,index(Elem[3],",") - 2)
  186.     printf \
  187.     "No successful conversation with %ssystem %s %s;\n%d waiting files.\n",
  188.     SysType[System],Elem[2],When,NumFiles[System]
  189.     }
  190. }
  191.  
  192. # Sets NoConSystems to systems with a last-conv file more than NoConWarn
  193. # days old, and NotPolledSystems to systems with a last-conv file more than
  194. # NotPolledWarn days old.
  195. function FindOldFiles(NoConSystems,NotPolledSystems,NoConWarn,NotPolledWarn,
  196. File) {
  197.     # Get lists of .system files that were last
  198.     # modified at warning threshold or longer ago
  199.     # Set NoConList to last-conv files more than NoConWarn days old, and
  200.     # NotPolledList to last-conv files more than NotPolledWarn days old.
  201.     if (!(FilesByDate(LogDir,".??*","+" NoConWarn,NoConList) + \
  202.     FilesByDate(LogDir,".??*","+" NotPolledWarn,NotPolledList)) && !debug)
  203.     return    # quit if no files found
  204.  
  205.     if (debug)
  206.     printf "Last-login files older than NoConWarn (%d) day(s): ",
  207.     NoConWarn
  208.     for (File in NoConList) {
  209.     if (debug)
  210.         printf "%s ",File
  211.     # get rid of leading .
  212.     NoConSystems[substr(File,2)]
  213.     }
  214.     if (debug)
  215.     print ""
  216.     if (debug)
  217.     printf "Last-login files older than NotPolledWarn (%d) day(s): ",
  218.     NotPolledWarn
  219.     for (File in NotPolledList) {
  220.     if (debug)
  221.         printf "%s ",File
  222.     NotPolledSystems[substr(File,2)]
  223.     }
  224.     if (debug)
  225.     print ""
  226. }
  227.  
  228. # Report last connection to systems.
  229. function DoLast(Long,LogDir,LCDir,Systems,ReverseSort,SiteSort,allSystems,All,
  230. System,LastConv,E,NFields,Time,NSys,Cmd,Month,Day,Hour,Minute,Second,CurYear,
  231. LastYear,Date,tty,Offset) {
  232.     # Get info from uucico logs
  233.     FindLastConv(LogDir,Systems,LastConv)
  234.     # Get info from last-conv status files
  235.     ReadLastConv(LCDir,Systems,LastConv,allSystems)
  236.     if (All)
  237.     CopySet(Systems,LastConv)
  238.     # Expected format of .systemname files:
  239.     #1    2      3 4  5  6  7  8     9  10  11           12       13    14
  240.     #uucp gorn  (1/25-16:28:20,17495,0) OK (conversation complete tty3C 1)
  241.     # month:3 day:4 hour:5 minute:6 second:7 tty:13
  242.     CurYear = strftime("%y")
  243.     LastYear = sprintf("%02d",CurYear - 1)
  244.     MakeTZOffset()
  245.     if (debug)
  246.     printf "TZ offset is %d\n",TZOffset
  247.     for (System in LastConv) {
  248.     NFields = split(LastConv[System],E,"[- \t(),/:]+")
  249.     if (NFields > 3) {
  250.         if (NFields < 13) {
  251.         printf "Bad last-connnection record (not enough fields):\n%s\n",
  252.         LastConv[System] > "/dev/stderr"
  253.         continue
  254.         }
  255.         tty = E[13]
  256.         Month = E[3]+0
  257.         Day = E[4]+0
  258.         Hour = E[5]+0
  259.         Minute = E[6]+0
  260.         Second = E[7]+0
  261.         if (Month < 1 || Month > 12 || Day < 1 || Day > 31 || Hour < 0 || \
  262.         Hour > 23 || Minute < 0 || Minute > 60 || Second < 0 || \
  263.         Second > 60) {
  264.         printf "Bad date or time in last-connnection record:\n%s\n",
  265.         LastConv[System] > "/dev/stderr"
  266.         continue
  267.         }
  268.  
  269.         Offset = (tty == "notty") ? 0 : TZOffset
  270.         Time = unixtime(CurYear,Month,Day,Hour,Minute,Second,Offset)
  271.         # Log files don't include year, so if the month/day put it
  272.         # in the future, assume it was from last year.
  273.         # Get new systime each time it is used, just in case
  274.         # logfiles are still being written.
  275.         if (Time > systime()) {
  276.         Time = \
  277.         unixtime(LastYear,Month,Day,Hour,Minute,Second,Offset)
  278.         if (debug)
  279.             printf "Record from previous year:\n%s\n",
  280.             LastConv[System] > "/dev/stderr"
  281.         }
  282.         # SCO uucpd does not set TZ, so uucico writes logfiles in GMT.
  283.         # Fortunately, this case can be recognized because uucico logs
  284.         # tty as "notty".  Use strftime() to convert to local time.
  285.         # Otherwise, avoid the possibility that the above calculations
  286.         # are incorrect by just reforatting the fields from the
  287.         # logfile.
  288.         if (toLocal && tty == "notty")
  289.         Date = strftime("%m/%d %T",Time)
  290.         else
  291.         Date = sprintf("%02d/%02d %02d:%02d:%02d",Month,Day,Hour,
  292.         Minute,Second)
  293.         if (Long)
  294.         PrintVal[System] = sprintf("%-8s  %s   %s",System,Date,tty)
  295.         else
  296.         PrintVal[System] = LastConv[System]
  297.         if (debug)
  298.         printf "Last login for %s was %s\n",System,
  299.         strftime("%y/%m/%d %T",Time) > "/dev/stderr"
  300.     }
  301.     else {
  302.         if (Long)
  303.         PrintVal[System] = sprintf("%-8s  NEVER",System)
  304.         else
  305.         PrintVal[System] = \
  306.         sprintf("---- %s  No successful conversations",System)
  307.         Time = 0
  308.     }
  309.     if (SiteSort)
  310.         SortVal[System] = System
  311.     else
  312.         SortVal[System] = sprintf("%010d",Time)
  313.     if (debug)
  314.         printf "Sort key for %s is: %s\n",System,SortVal[System] \
  315.         > "/dev/stderr"
  316.     }
  317.     if (debug)
  318.     printf "Sorting...\n" > "/dev/stderr"
  319.     NSys = qsortArbIndByValue(SortVal,k)
  320.     print "System    Last connection   TTY"
  321.     if (ReverseSort)
  322.     for (i = NSys; i >= 1; i--)
  323.         print PrintVal[k[i]]
  324.     else
  325.     for (i = 1; i <= NSys; i++)
  326.         print PrintVal[k[i]]
  327. }
  328.  
  329. function help(Name,Usage,LCDir,rcFile) {
  330.     printf \
  331. "%s: check whether remote UUCP systems have been successfully connected to.\n"\
  332. "%s\n"\
  333. "     A warning is printed for systems that have not been connected to in\n"\
  334. "a given period, by default one day.  This warning is printed only for\n"\
  335. "systems that are polled (called regularly regardless of whether there are\n"\
  336. "jobs) or which have UUCP jobs waiting; the warning is not printed for\n"\
  337. "systems that have no jobs waiting and which are never called or only\n"\
  338. "called on demand.\n"\
  339. "     A similar warning is also for systems that must poll us (systems that\n"\
  340. "are never called) that have jobs waiting.  By default, the no-connection\n"\
  341. "period is used for this warning, but a different value may be given with\n"\
  342. "-p; it might be set higher than the no-connection warning period as\n"\
  343. "failure to poll may be a common occurance and of less concern.\n"\
  344. "    %s searches the uucico logs for successful conversation lines.\n"\
  345. "If no successful conversation line is found for a system, it is read from\n"\
  346. "its last-conversation file, if it exists.\n"\
  347. "Options:\n"\
  348. "Some of the following options can also be set by assigning values to\n"\
  349. "variables in the configuration file %s.  Variables are\n"\
  350. "assigned to with the syntax:  varname=value  or in the case of flags, by\n"\
  351. "simply putting the indicated variable name in the file without a value.\n"\
  352. "Flag options can be turned off on the command line by following them\n"\
  353. "immediately with '-', e.g. -r- to turn off the r option in such a way that\n"\
  354. "it cannot be turned on in the config file.  Variable names appear in\n"\
  355. "parentheses in the option descriptions.\n"\
  356. "-a: Print warnings for or list last logins for all systems, including\n"\
  357. "    those who poll us and have no jobs queued up for them.  (ALL)\n"\
  358. "-u: The last-conversation file for each system that a successful\n"\
  359. "    conversation line is found for is udpated.  The successful\n"\
  360. "    conversation line is written to its last-conversation file,\n"\
  361. "    %s/.<system-name>\n"\
  362. "    %s should generally be run with the -u option once a day, immediately\n"\
  363. "    before the uucico logfiles are cleaned up (for example, by putting\n"\
  364. "    \"uucon -u\" near the top of the uudemon.clean script).  This allows\n"\
  365. "    long-term tracking of the last successful connection to a system.\n"\
  366. "-x<debug-level>: Turn on debugging output at level <debug-level>.  Higher\n"\
  367. "    values produce more output.\n"\
  368. "-t: By default, logfile times for incoming UUCP/TCP connections are taken\n"\
  369. "    to be in GMT (because SCO uucpd does not set TZ), and are converted to\n"\
  370. "    local time.  If -t is given, this conversion is not done.  (NOCONVERT)\n"\
  371. "-h: Print this help.\n"\
  372. "-L: Print last-conversation lines from the uucico logs or last-conversation\n"\
  373. "    files for all systems.  No warnings are printed, and no attempt is\n"\
  374. "    made to read the Systems file or to write to last-conversation files.\n"\
  375. "    Lines are printed in order from oldest to most recent connection.\n"\
  376. "-l: Like -L except that a header is printed and the lines are printed in\n"\
  377. "    a more readable format.\n"\
  378. "-s: Sort -l and -L output by site name.  (SITESORT)\n"\
  379. "-r: Print -l and -L output in reverse order.  (RSORT)\n"\
  380. "-c<no-con-warning-period>: Set the no-connection warning period to\n"\
  381. "    <no-con-warning-period> days.  This also set the not-polled warning\n"\
  382. "    period, unless -p is also given.  (NO_CON_WARN)\n"\
  383. "-p<not-polled-warning-period>: Set the not-polled warning period to\n"\
  384. "    <not-polled-warning-period> days.  (NOT_POLLED_WARN)\n"\
  385. "-d<log-dir>: Store last-conversation files for each system in <log-dir>\n"\
  386. "    instead of the default directory %s.  (LC_DIR)\n"\
  387. "-n: Do not read the configuration file (%s).\n",
  388.     Name,Usage,Name,rcFile,LCDir,Name,LCDir,rcFile
  389. }
  390.  
  391. function GetLastFiles(LCDir,LastConvFiles,allSystems,
  392. Cmd,LogFiles,Num,Elem,i,System) {
  393.     Cmd = "cd " LCDir "; echo .??*"
  394.     Cmd | getline LogFiles
  395.     close(Cmd)
  396.     Num = split(LogFiles,Elem," ")
  397.     for (i = 1; i <= Num; i++) {
  398.     System = Elem[i]
  399.     if (System == ".??*" || System == "")
  400.         continue
  401.     if (debug)
  402.         print "File found in last-conv dir: " System > "/dev/stderr"
  403.     System = substr(System,2)
  404.     if (!(System in allSystems))
  405.         printf \
  406.         "%s: Warning: In %s, found last-conversation file\n"\
  407.         "for a system that does not exist in the Systems file: %s\n",
  408.         Name,LCDir,System
  409.     else
  410.         LastConvFiles[System]
  411.     }
  412. }
  413.  
  414. # Read last-successful-conversation files of systems that do not have
  415. # a successful-conversation line in their uucico logfiles.
  416. function ReadLastConv(LCDir,Systems,LastConv,allSystems,
  417. File,System,LastConvFiles) {
  418.     # Find what last conv files exist, party to make sure there are not any
  419.     # obsolete files and party so we know whether to complain about files that
  420.     # cannot be opened.
  421.     GetLastFiles(LCDir,LastConvFiles,allSystems)
  422.     for (System in Systems) {
  423.     if ((!(System in LastConv) || LastConv[System] == "") &&
  424.     System in LastConvFiles) {
  425.         if (debug)
  426.         printf "Reading last-successful-conversation file for %s\n",
  427.         System
  428.         File = LCDir "/." System
  429.         if ((ret = (getline LastConv[System] < File)) != 1)
  430.         printf \
  431.         "Could not open status file \"%s\":\ngetline returned %d.\n",
  432.         File,ret
  433.         close(File)
  434.     }
  435.     else if (debug)
  436.         printf "Found successful-conversation line in uucico log for %s;\n"\
  437.         "skipping status file\n",System
  438.     }
  439. }
  440.  
  441. # Finds files that match Pattern and were last modified Days days
  442. # ago (a number optionally preceded by + or - as in find(C))
  443. # A list of the files is put in List
  444. # The number of matching files found is returned
  445. function FilesByDate(Dir,Pattern,Days,List,  num) {
  446.     num = 0
  447.     FindCmd = "cd " Dir "; find " Pattern " -mtime " Days " -print 2>&1"
  448.     while ((FindCmd | getline FileName) == 1)
  449.     if (FileName !~ "find: stat.. failed") {
  450.         List[FileName]
  451.         num++
  452.     }
  453.     close(FindCmd)
  454.     return num
  455. }
  456.  
  457. # Sets these:
  458. # WeCall: systems we call, from Systems
  459. # Never: systems we never call, from Systems
  460. # WePoll: systems we poll, from poll file
  461. #         Systems in poll file that are not in Systems file or are listed
  462. #         as Never called in Systems file are not included in WePoll.
  463. # Systems: all systems listed in Systems file
  464. function Setup(PollFile,Systems,WeCall,Never,WePoll,
  465. result,SystemsFile,Sysfiles,SysList,System) {
  466.     # SysList: all systems listed in uucp config, from poll file & Systems file
  467.     # read Sysfiles records & get name of uucico systems file
  468.     if (ReadRecFile("/usr/lib/uucp/Sysfiles",Sysfiles) != -1)
  469.     SystemsFile = FindSystemsFile(Sysfiles)
  470.     if (SystemsFile == "") {
  471.     if (debug)
  472.         print \
  473.     "No Sysfiles file; systems file is /usr/lib/uucp/Systems."
  474.     SystemsFile = "/usr/lib/uucp/Systems"
  475.     }
  476.     # Make WeCall[] indexes be the set of systems we call
  477.     while ((result = (getline < PollFile)) == 1)
  478.     if (($0 !~ "^#") && NF) {
  479.         System = substr($1,1,7)
  480.         WePoll[System]
  481.         SysList[System]
  482.     }
  483.     close(PollFile)
  484.     if (result < 0)
  485.     printf "Warning: error reading poll file \"%s\".\n",PollFile
  486.     while ((result = (getline < SystemsFile)) == 1) {
  487.     if (($0 ~ "^#") || !NF)        # ignore empty & comment lines
  488.         continue
  489.     System = substr($1,1,7)
  490.     if ($2 ~ "[nN]ever")
  491.         Never[System]
  492.     else
  493.         WeCall[System]
  494.     SysList[System]
  495.     Systems[System]
  496.     }
  497.     close(SystemsFile)
  498.     if (result < 0) {
  499.     printf "Error reading Systems file \"%s\".\n",SystemsFile
  500.     return 0
  501.     }
  502.     if (debug) {
  503.     printf "Systems we poll: "
  504.     for (Sys in WePoll)
  505.         printf Sys " "
  506.     printf "\nSystems we never call: "
  507.     for (Sys in Never)
  508.         printf Sys " "
  509.     printf "\nSystems we call: "
  510.     for (Sys in WeCall)
  511.         printf Sys " "
  512.     print ""
  513.     for (Sys in SysList)
  514.         if (Sys in WePoll) {
  515.         if (Sys in Never)
  516.             printf \
  517.         "Warning: System \"%s\" exists in Poll file %s,\n"\
  518.         "    but is listed as being never called in systems file %s.\n",
  519.             Sys,PollFile,SystemsFile
  520.         else if (Sys in WeCall)
  521.             printf \
  522.         "System \"%s\" exists in Poll file %s,\n"\
  523.         "    and is listed as being called in systems file %s.\n",
  524.             Sys,PollFile,SystemsFile
  525.         else
  526.             printf \
  527.         "Warning: System \"%s\" exists in Poll file %s,\n"\
  528.         "    but has no entry in Systems file \"%s\".\n",
  529.             Sys,PollFile,SystemsFile
  530.         }
  531.         else {
  532.         if (Sys in Never)
  533.             printf \
  534.         "System \"%s\" does not exist in Poll file %s,\n"\
  535.         "    and is listed as being never called in systems file %s.\n",
  536.             Sys,PollFile,SystemsFile
  537.         else if (Sys in WeCall)
  538.             printf \
  539.         "System \"%s\" does not exist in Poll file %s,\n"\
  540.         "    but is listed as being called in systems file %s.\n",
  541.         Sys,PollFile,SystemsFile
  542.         }
  543.     }
  544.     for (System in WePoll)
  545.     if (!(System in WeCall))
  546.         delete WePoll[System]
  547.     return 1
  548. }
  549.  
  550. # read File into Arr (one record per element of Arr).
  551. # Records are lines which are continued onto the next
  552. # line if they end with \.
  553. function ReadRecFile(File,Arr,result,i,line)
  554. {
  555.     i = -1
  556.     while ((result = (getline line < File)) == 1) {
  557.     if (line ~ "^#")
  558.         continue
  559.     if (Arr[i] ~ /\\[ \t]*$/) {
  560.         sub("\\\\[ \t]*$"," ",Arr[i])
  561.         Arr[i] = Arr[i] line
  562.     }
  563.     else
  564.         Arr[++i] = line
  565.     }
  566.     close(File)
  567.     return result
  568. }
  569.  
  570. # Find uucico record in arr generated from Systems file,
  571. # and return the value of its systems field.
  572. function FindSystemsFile(Sysfiles,i,tok,file,elem,var,systemsfile)
  573. {
  574.     for (i in Sysfiles) {
  575.     split(Sysfiles[i],tok,"[ \t]+")
  576.     var["service"] = ""
  577.     var["systems"] = "Systems"
  578.     for (file in tok) {
  579.         split(tok[file],elem,"=")
  580.         var[elem[1]] = elem[2]
  581.     }
  582.     if (var["service"] == "uucico") {
  583.         systemsfile = var["systems"]
  584.         if ( systemsfile !~ "^/" )
  585.         systemsfile = "/usr/lib/uucp/" systemsfile
  586.         return systemsfile
  587.     }
  588.     }
  589.     return ""
  590. }
  591.  
  592. # Find last successful connection to sites
  593. # by searching their log files for successful-conversation lines.
  594. function FindLastConv(LogDir,Systems,LastConv,
  595. Cmd,LogFiles,Names,System,Line,count,Elem,i,Num) {
  596.     Cmd = "cd " LogDir "; echo *"
  597.     Cmd | getline LogFiles
  598.     close(Cmd)
  599.     Num = split(LogFiles,Elem," ")
  600.     for (i = 1; i <= Num; i++) {
  601.     System = Elem[i]
  602.     if (System == "*" || System == "")
  603.         continue
  604.     if (debug)
  605.         print "File found in log dir: " System > "/dev/stderr"
  606.     Names[System]
  607.     count++
  608.     }
  609.     if (!count) {
  610.     if (debug)
  611.         printf "No log files found in %s.\n",LogDir
  612.     return
  613.     }
  614.     
  615.     # for each system that has a log file...
  616.     if (debug)
  617.     printf "Reading logfiles: "
  618.     for (System in Systems)
  619.     if (System in Names) {
  620.         if (debug)
  621.         printf "%s ",System
  622.         LastConv[System]
  623.         LogFile = LogDir "/" System
  624.         while ((getline Line < LogFile) == 1)
  625.         if (Line ~ /OK \(conversation/)
  626.             LastConv[System] = Line
  627.         close(LogFile)
  628.     }
  629.     if (debug)
  630.     print ""
  631. }
  632.  
  633. # Writes last successful conversation lines for systems to a last-conversation
  634. # file. If the last conversation line is empty, meaning that the system had a
  635. # logfile but no successful-conversation line was found, and does not already
  636. # have a last-conversation file, an initial last-sucessful-conversation file is
  637. # created for it anyway.
  638. function SaveLastConv(LCDir,LastConv,
  639. System,TrackFile,TrackFiles) {
  640.     if (debug)
  641.     print "Writing logfiles..."
  642.     for (System in LastConv) {
  643.     TrackFile = LogDir "/." System
  644.     if (LastConv[System] != "") {
  645.         if (debug)
  646.         printf "Last conversation line for system \"%s\":\n%s\n",
  647.         System, LastConv[System]
  648.         print LastConv[System] > TrackFile
  649.         TrackFiles = TrackFiles " " TrackFile
  650.     }
  651.     else if    ((getline < TrackFile) != 1) {
  652.         # if no last-successful-conversation file...
  653.         if (debug)
  654.         printf "No conversation line found for system \"%s\"\n",File
  655.         print "uucp " System " NEVER" > TrackFile
  656.         TrackFiles = TrackFiles " " TrackFile
  657.     }
  658.     close(TrackFile)
  659.     }
  660.     if (TrackFiles != "")
  661.     system("chmod a+r" TrackFiles)
  662. }
  663.  
  664. ### Start of ProcArgs library
  665. # @(#) ProcArgs 1.11 96/12/08
  666. # 92/02/29 john h. dubois iii (john@armory.com)
  667. # 93/07/18 Added "#" arg type
  668. # 93/09/26 Do not count -h against MinArgs
  669. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  670. #          Removed meaning of "+" or "-" by itself.
  671. # 94/03/08 Added & option and *()< option types.
  672. # 94/04/02 Added NoRCopt to Opts()
  673. # 94/06/11 Mark numeric variables as such.
  674. # 94/07/08 Opts(): Do not require any args if h option is given.
  675. # 95/01/22 Record options given more than once.  Record option num in argv.
  676. # 95/06/08 Added ExclusiveOptions().
  677. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  678. #          Expand $VARNAME at the start of its filenames.
  679. #          Let varname=0 and -option- turn off an option.
  680. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  681. #          of the vars should be searched for in the environment.
  682. #          Check for duplicate rcfiles.
  683. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  684. #          now return various negatives values on error, not just -1, and
  685. #          Opts() may set Err to various positive values, not just 1.
  686. #          Added AllowUnrecOpt.
  687. # 96/05/23 Check type given for & option
  688. # 96/06/15 Re-port to awk
  689. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  690. #          used by other functions.
  691. # 96/10/15 Added OptChars
  692. # 96/11/01 Added exOpts arg to Opts()
  693. # 96/11/16 Added ; type
  694. # 96/12/08 Added Opt2Set() & Opt2Sets()
  695. # 96/12/27 Added CmdLineOpt()
  696.  
  697. # optlist is a string which contains all of the possible command line options.
  698. # A character followed by certain characters indicates that the option takes
  699. # an argument, with type as follows:
  700. # :    String argument
  701. # ;    Non-empty string argument
  702. # *    Floating point argument
  703. # (    Non-negative floating point argument
  704. # )    Positive floating point argument
  705. # #    Integer argument
  706. # <    Non-negative integer argument
  707. # >    Positive integer argument
  708. # The only difference the type of argument makes is in the runtime argument
  709. # error checking that is done.
  710.  
  711. # The & option is a special case used to get numeric options without the
  712. # user having to give an option character.  It is shorthand for [-+.0-9].
  713. # If & is included in optlist and an option string that begins with one of
  714. # these characters is seen, the value given to "&" will include the first
  715. # char of the option.  & must be followed by a type character other than ":"
  716. # or ";".
  717. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  718.  
  719. # Strings in argv[] which begin with "-" or "+" are taken to be
  720. # strings of options, except that a string which consists solely of "-"
  721. # or "+" is taken to be a non-option string; like other non-option strings,
  722. # it stops the scanning of argv and is left in argv[].
  723. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  724. # If an option takes an argument, the argument may either immediately
  725. # follow it or be given separately.
  726. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  727. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  728. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  729. # this feature had a flaw that caused problems in some cases.  See the OptChars
  730. # parameter to explicitly set the option-specifier characters.
  731.  
  732. # If an option that does not take an argument is given,
  733. # an index with its name is created in Options and its value is set to the
  734. # number of times it occurs in argv[].
  735.  
  736. # If an option that does take an argument is given, an index with its name is
  737. # created in Options and its value is set to the value of the argument given
  738. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  739. # If an option that takes an argument is given more than once,
  740. # Options[option-name,"count"] is incremented, and the value is assigned to
  741. # the index (option-name,instance) where instance is 2 for the second occurance
  742. # of the option, etc.
  743. # In other words, the first time an option with a value is encountered, the
  744. # value is assigned to an index consisting only of its name; for any further
  745. # occurances of the option, the value index has an extra (count) dimension.
  746.  
  747. # The sequence number for each option found in argv[] is stored in
  748. # Options[option-name,"num",instance], where instance is 1 for the first
  749. # occurance of the option, etc.  The sequence number starts at 1 and is
  750. # incremented for each option, both those that have a value and those that
  751. # do not.  Options set from a config file have a value of 0 assigned to this.
  752.  
  753. # Options and their arguments are deleted from argv.
  754. # Note that this means that there may be gaps left in the indices of argv[].
  755. # If compress is nonzero, argv[] is packed by moving its elements so that
  756. # they have contiguous integer indices starting with 0.
  757. # Option processing will stop with the first unrecognized option, just as
  758. # though -- was given except that unlike -- the unrecognized option will not be
  759. # removed from ARGV[].  Normally, an error value is returned in this case.
  760. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  761. # be found, so the number of remaining arguments is returned instead.
  762. # If OptChars is not a null string, it is the set of characters that indicate
  763. # that an argument is an option string if the string begins with one of the
  764. # characters.  A string consisting solely of two of the same option-indicator
  765. # characters stops the scanning of argv[].  The default is "-+".
  766. # argv[0] is not examined.
  767. # The number of arguments left in argc is returned.
  768. # If an error occurs, the global string OptErr is set to an error message
  769. # and a negative value is returned.
  770. # Current error values:
  771. # -1: option that required an argument did not get it.
  772. # -2: argument of incorrect type supplied for an option.
  773. # -3: unrecognized (invalid) option.
  774. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  775. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  776. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  777. {
  778. # ArgNum is the index of the argument being processed.
  779. # ArgsLeft is the number of arguments left in argv.
  780. # Arg is the argument being processed.
  781. # ArgLen is the length of the argument being processed.
  782. # ArgInd is the position of the character in Arg being processed.
  783. # Option is the character in Arg being processed.
  784. # Pos is the position in OptList of the option being processed.
  785. # NumOpt is true if a numeric option may be given.
  786.     ArgsLeft = argc
  787.     NumOpt = index(OptList,"&")
  788.     OptionNum = 0
  789.     if (OptChars == "")
  790.     OptChars = "-+"
  791.     while (OptChars != "") {
  792.     c = substr(OptChars,1,1)
  793.     OptChars = substr(OptChars,2)
  794.     OptCharSet[c]
  795.     OptTerm[c c]
  796.     }
  797.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  798.     Arg = argv[ArgNum]
  799.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  800.         break    # Not an option; quit
  801.     if (Arg in OptTerm) {
  802.         delete argv[ArgNum]
  803.         ArgsLeft--
  804.         break
  805.     }
  806.     ArgLen = length(Arg)
  807.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  808.         Option = substr(Arg,ArgInd,1)
  809.         if (NumOpt && Option ~ /[-+.0-9]/) {
  810.         # If this option is a numeric option, make its flag be & and
  811.         # its option string flag position be the position of & in
  812.         # the option string.
  813.         Option = "&"
  814.         Pos = NumOpt
  815.         # Prefix Arg with a char so that ArgInd will point to the
  816.         # first char of the numeric option.
  817.         Arg = "&" Arg
  818.         ArgLen++
  819.         }
  820.         # Find position of flag in option string, to get its type (if any).
  821.         # Disallow & as literal flag.
  822.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  823.         if (AllowUnrecOpt) {
  824.             Escape = 1
  825.             break
  826.         }
  827.         else {
  828.             OptErr = "Invalid option: " specGiven Option
  829.             return -3
  830.         }
  831.         }
  832.  
  833.         # Find what the value of the option will be if it takes one.
  834.         # NeedNextOpt is true if the option specifier is the last char of
  835.         # this arg, which means that if the option requires a value it is
  836.         # the next arg.
  837.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  838.         if (GotValue = ArgNum + 1 < argc)
  839.             Value = argv[ArgNum+1]
  840.         }
  841.         else {    # Value is included with option
  842.         Value = substr(Arg,ArgInd + 1)
  843.         GotValue = 1
  844.         }
  845.  
  846.         if (HadValue = AssignVal(Option,Value,Options,
  847.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  848.         specGiven)) {
  849.         if (HadValue < 0)    # error occured
  850.             return HadValue
  851.         if (HadValue == 2)
  852.             ArgInd++    # Account for the single-char value we used.
  853.         else {
  854.             if (NeedNextOpt) {    # option took next arg as value
  855.             delete argv[++ArgNum]
  856.             ArgsLeft--
  857.             }
  858.             break    # This option has been used up
  859.         }
  860.         }
  861.     }
  862.     if (Escape)
  863.         break
  864.     # Do not delete arg until after processing of it, so that if it is not
  865.     # recognized it can be left in ARGV[].
  866.     delete argv[ArgNum]
  867.     ArgsLeft--
  868.     }
  869.     if (compress != 0) {
  870.     dest = 1
  871.     src = argc - ArgsLeft + 1
  872.     for (count = ArgsLeft - 1; count; count--) {
  873.         ARGV[dest] = ARGV[src]
  874.         dest++
  875.         src++
  876.     }
  877.     }
  878.     return ArgsLeft
  879. }
  880.  
  881. # Assignment to values in Options[] occurs only in this function.
  882. # Option: Option specifier character.
  883. # Value: Value to be assigned to option, if it takes a value.
  884. # Options[]: Options array to return values in.
  885. # ArgType: Argument type specifier character.
  886. # GotValue: Whether any value is available to be assigned to this option.
  887. # Name: Name of option being processed.
  888. # OptionNum: Number of this option (starting with 1) if set in argv[],
  889. #     or 0 if it was given in a config file or in the environment.
  890. # SingleOpt: true if the value (if any) that is available for this option was
  891. #     given as part of the same command line arg as the option.  Used only for
  892. #     options from the command line.
  893. # specGiven is the option specifier character use, if any (e.g. - or +),
  894. # for use in error messages.
  895. # Global variables: OptErr
  896. # Return value: negative value on error, 0 if option did not require an
  897. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  898. # the arg.
  899. # Current error values:
  900. # -1: Option that required an argument did not get it.
  901. # -2: Value of incorrect type supplied for option.
  902. # -3: Bad type given for option &
  903. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  904. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  905.     # If option takes a value...    [
  906.     NumTypes = "*()#<>]"
  907.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  908.     OptErr = "Bad type given for & option"
  909.     return -3
  910.     }
  911.  
  912.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  913.     if (!GotValue) {
  914.         if (Name != "")
  915.         OptErr = "Variable requires a value -- " Name
  916.         else
  917.         OptErr = "option requires an argument -- " Option
  918.         return -1
  919.     }
  920.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  921.         OptErr = Err
  922.         return -2
  923.     }
  924.     # Mark this as a numeric variable; will be propogated to Options[] val.
  925.     if (ArgType != ":" && ArgType != ";")
  926.         Value += 0
  927.     if ((Instance = ++Options[Option,"count"]) > 1)
  928.         Options[Option,Instance] = Value
  929.     else
  930.         Options[Option] = Value
  931.     }
  932.     # If this is an environ or rcfile assignment & it was given a value...
  933.     else if (!OptionNum && Value != "") {
  934.     UsedValue = 1
  935.     # If the value is "0" or "-" and this is the first instance of it,
  936.     # do not set Options[Option]; this allows an assignment in an rcfile to
  937.     # turn off an option (for the simple "Option in Options" test) in such
  938.     # a way that it cannot be turned on in a later file.
  939.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  940.         Instance = 1
  941.     else
  942.         Instance = ++Options[Option]
  943.     # Save the value even though this is a flag
  944.     Options[Option,Instance] = Value
  945.     }
  946.     # If this is a command line flag and has a - following it in the same arg,
  947.     # it is being turned off.
  948.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  949.     UsedValue = 2
  950.     if (Option in Options)
  951.         Instance = ++Options[Option]
  952.     else
  953.         Instance = 1
  954.     Options[Option,Instance]
  955.     }
  956.     # If this is a flag assignment without a value, increment the count for the
  957.     # flag unless it was turned off.  The indicator for a flag being turned off
  958.     # is that the flag index has not been set in Options[] but it has an
  959.     # instance count.
  960.     else if (Option in Options || !((Option,1) in Options))
  961.     # Increment number of times this flag seen; will inc null value to 1
  962.     Instance = ++Options[Option]
  963.     Options[Option,"num",Instance] = OptionNum
  964.     return UsedValue
  965. }
  966.  
  967. # Option is the option letter
  968. # Value is the value being assigned
  969. # Name is the var name of the option, if any
  970. # ArgType is one of:
  971. # :    String argument
  972. # ;    Non-null string argument
  973. # *    Floating point argument
  974. # (    Non-negative floating point argument
  975. # )    Positive floating point argument
  976. # #    Integer argument
  977. # <    Non-negative integer argument
  978. # >    Positive integer argument
  979. # specGiven is the option specifier character use, if any (e.g. - or +),
  980. # for use in error messages.
  981. # Returns null on success, err string on error
  982. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  983.     if (ArgType == ":")
  984.     return ""
  985.     if (ArgType == ";") {
  986.     if (Value == "")
  987.         Err = "must be a non-empty string"
  988.     }
  989.     # A number begins with optional + or -, and is followed by a string of
  990.     # digits or a decimal with digits before it, after it, or both
  991.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  992.     Err = "must be a number"
  993.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  994.     Err = "may not include a fraction"
  995.     else if (ArgType ~ "[()<>]" && Value < 0)
  996.     Err = "may not be negative"
  997.     # (
  998.     else if (ArgType ~ "[)>]" && Value == 0)
  999.     Err = "must be a positive number"
  1000.     if (Err != "") {
  1001.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1002.     if (Name != "")
  1003.         return ErrStr "variable " substr(Name,1,1) " " Err
  1004.     else {
  1005.         if (Option == "&")
  1006.         Option = Value
  1007.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1008.     }
  1009.     }
  1010.     else
  1011.     return ""
  1012. }
  1013.  
  1014. # Note: only the above functions are needed by ProcArgs.
  1015. # The rest of these functions call ProcArgs() and also do other
  1016. # option-processing stuff.
  1017.  
  1018. # Opts: Process command line arguments.
  1019. # Opts processes command line arguments using ProcArgs()
  1020. # and checks for errors.  If an error occurs, a message is printed
  1021. # and the program is exited.
  1022. #
  1023. # Input variables:
  1024. # Name is the name of the program, for error messages.
  1025. # Usage is a usage message, for error messages.
  1026. # OptList the option description string, as used by ProcArgs().
  1027. # MinArgs is the minimum number of non-option arguments that this
  1028. # program should have, non including ARGV[0] and +h.
  1029. # If the program does not require any non-option arguments,
  1030. # MinArgs should be omitted or given as 0.
  1031. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1032. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1033. # by the value of the environment variable HOME.  If a filename begins with
  1034. # $, the part from the character after the $ up until (but not including)
  1035. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1036. # environment; if found its value will be substituted, if not the filename will
  1037. # be discarded.
  1038. # rcfiles are read in the order given.
  1039. # Values given in them will not override values given on the command line,
  1040. # and values given in later files will not override those set in earlier
  1041. # files, because AssignVal() will store each with a different instance index.
  1042. # The first instance of each variable, either on the command line or in an
  1043. # rcfile, will be stored with no instance index, and this is the value
  1044. # normally used by programs that call this function.
  1045. # VarNames is a comma-separated list of variable names to map to options,
  1046. # in the same order as the options are given in OptList.
  1047. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1048. # searched for in the environment.  If set to -1, all values will be searched
  1049. # for in the environment.  Values given in the environment will override
  1050. # those given in the rcfiles but not those given on the command line.
  1051. # NoRCopt, if given, is an additional letter option that if given on the
  1052. # command line prevents the rcfiles from being read.
  1053. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1054. # ExclusiveOptions() for a description of exOpts.
  1055. # Special options:
  1056. # If x is made an option and is given, some debugging info is output.
  1057. # h is assumed to be the help option.
  1058.  
  1059. # Global variables:
  1060. # The command line arguments are taken from ARGV[].
  1061. # The arguments that are option specifiers and values are removed from
  1062. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1063. # The number of elements in ARGV[] should be in ARGC.
  1064. # After processing, ARGC is set to the number of elements left in ARGV[].
  1065. # The option values are put in Options[].
  1066. # On error, Err is set to a positive integer value so it can be checked for in
  1067. # an END block.
  1068. # Return value: The number of elements left in ARGV is returned.
  1069. # Must keep OptErr global since it may be set by InitOpts().
  1070. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1071. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1072.     if (MinArgs == "")
  1073.     MinArgs = 0
  1074.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1075.     optChars)
  1076.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1077.     if (ArgsLeft >= 0) {
  1078.         OptErr = "Not enough arguments"
  1079.         Err = 4
  1080.     }
  1081.     else
  1082.         Err = -ArgsLeft
  1083.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1084.     Name,OptErr,Usage > "/dev/stderr"
  1085.     exit 1
  1086.     }
  1087.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1088.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1089.     {
  1090.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1091.     Err = -e
  1092.     exit 1
  1093.     }
  1094.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1095.     {
  1096.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1097.     Err = 1
  1098.     exit 1
  1099.     }
  1100.     return ArgsLeft
  1101. }
  1102.  
  1103. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1104. # <variable-name><assignment-char><value>.
  1105. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1106. # line and whitespace between the variable name and the assignment character) 
  1107. # is stripped.  Lines that do not contain an assignment operator or which
  1108. # contain a null variable name are ignored, other than possibly being noted in
  1109. # the return value.  If more than one assignment is made to a variable, the
  1110. # first assignment is used.
  1111. # Input variables:
  1112. # File is the file to read.
  1113. # Comment is the line-comment character.  If it is found as the first non-
  1114. #     whitespace character on a line, the line is ignored.
  1115. # Assign is the assignment string.  The first instance of Assign on a line
  1116. #     separates the variable name from its value.
  1117. # If StripWhite is true, whitespace around the value (whitespace between the
  1118. #     assignment char and trailing whitespace on the line) is stripped.
  1119. # VarPat is a pattern that variable names must match.  
  1120. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1121. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1122. #     a line; no assignment operator is needed.  These variables are set in
  1123. #     the output array with a null value.  Lines containing nothing but
  1124. #     whitespace are still ignored.
  1125. # Output variables:
  1126. # Values[] contains the assignments, with the indexes being the variable names
  1127. #     and the values being the assigned values.
  1128. # Lines[] contains the line number that each variable occured on.  A flag set
  1129. #     is record by giving it an index in Lines[] but not in Values[].
  1130. # Return value:
  1131. # If any errors occur, a string consisting of descriptions of the errors
  1132. # separated by newlines is returned.  In no case will the string start with a
  1133. # numeric value.  If no errors occur,  the number of lines read is returned.
  1134. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1135. FlagsOK,
  1136. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1137.     if (Comment != "")
  1138.     Comment = "^" Comment
  1139.     AssignLen = length(Assign)
  1140.     if (VarPat == "")
  1141.     VarPat = "."    # null varname not allowed
  1142.     while ((Status = (getline Line < File)) == 1) {
  1143.     LineNum++
  1144.     sub("^[ \t]+","",Line)
  1145.     if (Line == "")        # blank line
  1146.         continue
  1147.     if (Comment != "" && Line ~ Comment)
  1148.         continue
  1149.     if (Pos = index(Line,Assign)) {
  1150.         Var = substr(Line,1,Pos-1)
  1151.         Val = substr(Line,Pos+AssignLen)
  1152.         if (StripWhite) {
  1153.         sub("^[ \t]+","",Val)
  1154.         sub("[ \t]+$","",Val)
  1155.         }
  1156.     }
  1157.     else {
  1158.         Var = Line    # If no value, var is entire line
  1159.         Val = ""
  1160.     }
  1161.     if (!FlagsOK && Val == "") {
  1162.         Errs = Errs \
  1163.         sprintf("\nBad assignment on line %d of file %s: %s",
  1164.         LineNum,File,Line)
  1165.         continue
  1166.     }
  1167.     sub("[ \t]+$","",Var)
  1168.     if (Var !~ VarPat) {
  1169.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1170.         LineNum,File,Var)
  1171.         continue
  1172.     }
  1173.     if (!(Var in Lines)) {
  1174.         Lines[Var] = LineNum
  1175.         if (Pos)
  1176.         Values[Var] = Val
  1177.     }
  1178.     }
  1179.     if (Status)
  1180.     Errs = Errs "\nCould not read file " File
  1181.     close(File)
  1182.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1183. }
  1184.  
  1185. # Variables:
  1186. # Data is stored in Options[].
  1187. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1188. # Global vars:
  1189. # Sets OptErr.  Uses ENVIRON[].
  1190. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1191. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1192. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1193. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1194.     split("",filesRead,"")    # make awk know this is an array
  1195.     NumVars = split(VarNames,Vars,",")
  1196.     TypesInd = Ret = 0
  1197.     if (EnvSearch == -1)
  1198.     EnvSearch = NumVars
  1199.     for (i = 1; i <= NumVars; i++) {
  1200.     Var = Vars[i]
  1201.     CharOpt = substr(OptList,++TypesInd,1)
  1202.     if (CharOpt ~ "^[:;*()#<>&]$")
  1203.         CharOpt = substr(OptList,++TypesInd,1)
  1204.     Map[Var] = CharOpt
  1205.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1206.     # Do not overwrite entries from environment
  1207.     if (i <= EnvSearch && Var in ENVIRON &&
  1208.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1209.         return Err
  1210.     }
  1211.  
  1212.     numrcFiles = split(rcFiles,fNames,":")
  1213.     for (i = 1; i <= numrcFiles; i++) {
  1214.     rcFile = fNames[i]
  1215.     if (rcFile ~ "^~/")
  1216.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1217.     else if (rcFile ~ /^\$/) {
  1218.         rcFile = substr(rcFile,2)
  1219.         match(rcFile,"^[a-zA-Z0-9_]*")
  1220.         envvar = substr(rcFile,1,RLENGTH)
  1221.         if (envvar in ENVIRON)
  1222.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1223.         else
  1224.         continue
  1225.     }
  1226.     if (rcFile in filesRead)
  1227.         continue
  1228.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1229.     # may be the same
  1230.     filesRead[rcFile]
  1231.     if ("x" in Options)
  1232.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1233.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1234.     if (retStr > 0)
  1235.         READ_RCFILE = 1
  1236.     else if (ret != "") {
  1237.         OptErr = retStr
  1238.         Ret = -1
  1239.     }
  1240.     for (Var in Lines)
  1241.         if (Var in Map) {
  1242.         if ((Err = AssignVal(Map[Var],
  1243.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1244.         Var in Values,Var,0)) < 0)
  1245.             return Err
  1246.         }
  1247.         else {
  1248.         OptErr = sprintf(\
  1249.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1250.         Lines[Var],rcFile)
  1251.         Ret = -1
  1252.         }
  1253.     }
  1254.  
  1255.     if ("x" in Options)
  1256.     for (Var in Map)
  1257.         if (Map[Var] in Options)
  1258.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1259.         "/dev/stderr"
  1260.         else
  1261.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1262.     return Ret
  1263. }
  1264.  
  1265. # OptSets is a semicolon-separated list of sets of option sets.
  1266. # Within a list of option sets, the option sets are separated by commas.  For
  1267. # each set of sets, if any option in one of the sets is in Options[] AND any
  1268. # option in one of the other sets is in Options[], an error string is returned.
  1269. # If no conflicts are found, nothing is returned.
  1270. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1271. # the exclusions presented by the first set of sets (ab,def,g) if:
  1272. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1273. # (a or b is in Options[]) AND (g is in Options) OR
  1274. # (d, e, or f is in Options[]) AND (g is in Options)
  1275. # An error will be returned due to the exclusions presented by the second set
  1276. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1277. # todo: make options given on command line unset options given in config file
  1278. # todo: that they conflict with.
  1279. function ExclusiveOptions(OptSets,Options,
  1280. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1281. SetNum,OSetNum) {
  1282.     NumSetSets = split(OptSets,SetSets,";")
  1283.     # For each set of sets...
  1284.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1285.     # NumSets is the number of sets in this set of sets.
  1286.     NumSets = split(SetSets[SetSet],Sets,",")
  1287.     # For each set in a set of sets except the last...
  1288.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1289.         s1 = Sets[SetNum]
  1290.         L1 = length(s1)
  1291.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1292.         # If any of the options in this set was given, check whether
  1293.         # any of the options in the other sets was given.  Only check
  1294.         # later sets since earlier sets will have already been checked
  1295.         # against this set.
  1296.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1297.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1298.             s2 = Sets[OSetNum]
  1299.             L2 = length(s2)
  1300.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1301.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1302.                 ErrStr = ErrStr "\n"\
  1303.                 sprintf("Cannot give both %s and %s options.",
  1304.                 c1,c2)
  1305.             }
  1306.     }
  1307.     }
  1308.     if (ErrStr != "")
  1309.     return substr(ErrStr,2)
  1310.     return ""
  1311. }
  1312.  
  1313. # The value of each instance of option Opt that occurs in Options[] is made an
  1314. # index of Set[].
  1315. # The return value is the number of instances of Opt in Options.
  1316. function Opt2Set(Options,Opt,Set,  count) {
  1317.     if (!(Opt in Options))
  1318.     return 0
  1319.     Set[Options[Opt]]
  1320.     count = Options[Opt,"count"]
  1321.     for (; count > 1; count--)
  1322.     Set[Options[Opt,count]]
  1323.     return count
  1324. }
  1325.  
  1326. # The value of each instance of option Opt that occurs in Options[] that
  1327. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1328. # Other values are made indexes of Set[].
  1329. # The return value is the number of instances of Opt in Options.
  1330. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1331.     ret = Opt2Set(Options,Opt,aSet)
  1332.     for (value in aSet)
  1333.     if (substr(value,1,1) == "!")
  1334.         nSet[substr(value,2)]
  1335.     else
  1336.         Set[value]
  1337.     return ret
  1338. }
  1339.  
  1340. # Returns true if option Opt was given on the command line.
  1341. function CmdLineOpt(Options,Opt,  i) {
  1342.     for (i = 1; (Opt,"num",i) in Options; i++)
  1343.     if (Options[Opt,"num",i] != 0)
  1344.         return 1
  1345.     return 0
  1346. }
  1347. ### End of ProcArgs library
  1348. ### Begin array routines
  1349.  
  1350. # InitArr: Initialize an array with values.
  1351. # Ind and Vals are separated into lists on Sep.
  1352. # For each item in Ind, an index with that name is created in Arr[],
  1353. # and the value with the same position in Vals is stored in it.
  1354. # Global variables: none.
  1355. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  1356.     split(Ind,indnames,sep)
  1357.     split(Vals,values,sep)
  1358.     for (numind in indnames)
  1359.     Arr[indnames[numind]] = values[numind]
  1360. }
  1361.  
  1362. function ClearArr(Arr,  Elem) {
  1363.     for (Elem in Arr)
  1364.     delete Arr[Elem]
  1365. }
  1366.  
  1367. function CopyArr(From,To,  Elem) {
  1368.     for (Elem in From)
  1369.     To[Elem] = From[Elem]
  1370. }
  1371.  
  1372. # Subtract the values in Subtrahend from those in Minuend
  1373. function SubtractArr(Minuend,Subtrahend,  Elem) {
  1374.     for (Elem in Subtrahend)
  1375.     Minuend[Elem] -= Subtrahend[Elem]
  1376. }
  1377. # For each element of the array In, an element is created in Out having
  1378. # an index equal to the value of the element in In and a value equal to 
  1379. # the index of the element in In.
  1380. function Invert(In,Out,  Index) {
  1381.     for (Index in In)
  1382.     Out[In[Index]] = Index
  1383. }
  1384.  
  1385. # Assign: make an array from a list of assignments.
  1386. # An index with the name of each variable in the list is created in the array.
  1387. # Its value is set to the value given for it.
  1388. # Input variables: 
  1389. # Elements is a string containing the list of variable-value pairs.
  1390. # Sep is the string that separates the pairs in the list.
  1391. # AssignOp is the string that separates variables from values.
  1392. # Output variables:
  1393. # Arr is the array.
  1394. # Return value: the number of elements added to the set.
  1395. # Example:
  1396. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  1397. function Assign(Arr,Elements,Sep,AssignOp,
  1398. Num,Names,Elem,Assignments,Assignment,i) {
  1399.     Num = split(Elements,Assignments,Sep)
  1400.     for (i = 1; i <= Num; i++) {
  1401.     Assignment = Assignments[i]
  1402.     Ind = index(Assignment,AssignOp)
  1403.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  1404.     }
  1405.     return Num
  1406. }
  1407.  
  1408. # Packs Arr[], which should have integer indices starting at or above n, to
  1409. # contiguous integer indices starting with n.
  1410. # If n is not given it defaults to 0.
  1411. # Num should be the number of elements in Arr.
  1412. function PackArr(Arr,Num,n,  NewInd,OldInd) {
  1413.     NewInd = OldInd = n+0
  1414.     for (; Num; Num--) {
  1415.     while (!(OldInd in Arr))
  1416.         OldInd++
  1417.     if (NewInd != OldInd) {
  1418.         Arr[NewInd] = Arr[OldInd]
  1419.         delete Arr[OldInd]
  1420.     }
  1421.     OldInd++
  1422.     NewInd++
  1423.     }
  1424. }
  1425. ### End array routines
  1426. ### Begin set library
  1427. # 96/05/23 added return values  jhdiii
  1428. # 96/05/25 added set2list()
  1429.  
  1430. # Return value: the number of new elements added to Inter
  1431. function Intersection(A,B,Inter,  Elem,Count) {
  1432.     for (Elem in A)
  1433.     if (Elem in B && !(Elem in Inter)) {
  1434.         Inter[Elem]
  1435.         Count++
  1436.     }
  1437.     return Count
  1438. }
  1439.  
  1440. # Return value: the number of new elements added to Both
  1441. function Union(A,B,Both) {
  1442.     return CopySet(A,Both) + CopySet(B,Both)
  1443. }
  1444.  
  1445. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  1446. # Return value: the number of elements deleted.
  1447. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  1448.     for (Elem in Subtrahend)
  1449.     if (Elem in Minuend) {
  1450.         delete Minuend[Elem]
  1451.         nDel++
  1452.     }
  1453.     return nDel
  1454. }
  1455.  
  1456. # Return value: the number of new elements added to To
  1457. function CopySet(From,To,  Elem,n) {
  1458.     for (Elem in From)
  1459.     if (!(Elem in To)) {
  1460.         To[Elem]
  1461.         n++
  1462.     }
  1463.     return n
  1464. }
  1465.  
  1466. # Returns 1 if Set is empty, 0 if not.
  1467. function IsEmpty(Set,  i) {
  1468.     for (i in Set)
  1469.     return 0
  1470.     return 1
  1471. }
  1472.  
  1473. # MakeSet: make a set from a list.
  1474. # An index with the name of each element of the list is created in the given
  1475. # array.
  1476. # Input variables: 
  1477. # Elements is a string containing the list of elements.
  1478. # Sep is the character that separates the elements of the list.
  1479. # Output variables:
  1480. # Set is the array.
  1481. # Return value: the number of new elements added to the set.
  1482. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  1483.     nFound = 0
  1484.     Num = split(Elements,Names,Sep)
  1485.     for (i = 1; i <= Num; i++) {
  1486.     ind = Names[i]
  1487.     if (!(ind in Set)) {
  1488.         Set[ind]
  1489.         nFound++
  1490.     }
  1491.     }
  1492.     return nFound
  1493. }
  1494.  
  1495. # Returns the number of elements in set Set
  1496. function NumElem(Set,  elem,Num) {
  1497.     for (elem in Set)
  1498.     Num++
  1499.     return Num
  1500. }
  1501.  
  1502. # Remove all elements from Set
  1503. function DeleteAll(Set,  i) {
  1504.     split("",Set,",")
  1505. }
  1506.  
  1507. # Returns a list of all of the elements in Set[], with each pair of elements
  1508. # separated by Sep.
  1509. function set2list(Set,Sep,  list,elem) {
  1510.     for (elem in Set)
  1511.     list = list Sep elem
  1512.     return substr(list,2)    # skip 1st separator
  1513. }
  1514. ### End set library
  1515. ### Begin timedate routines.
  1516. # These functions operate on absolute dates & times.
  1517.  
  1518. # convert month/day or year/month/day date to yymmdd date
  1519. # uses global "year" var if year not given
  1520. function makedate(InDate,Elements,d,date) {
  1521.     Elements = split(InDate,d,"/")
  1522.     date = d[1] * 100 + d[2]
  1523.     if (Elements == 2)
  1524.     date += year
  1525.     else if (Elements == 3)
  1526.     date = date * 100 + d[3]
  1527.     else
  1528.     return -1
  1529.     return date
  1530. }
  1531.  
  1532. # convert yymmdd date to yy/mm/dd date
  1533. function unmakedate(Date) {
  1534.     return substr(Date,1,2) "/" substr(Date,3,2) "/" substr(Date,5,2)
  1535. }
  1536.  
  1537. function MkMonth2Num(  Month) {
  1538.     split("Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",Months,",")
  1539.     for (Month in Months)
  1540.     Month2Num[Months[Month]] = sprintf("%02d",Month)
  1541. }
  1542.  
  1543. # Takes a date and stores its components in the following elements of Date[]:
  1544. # year    yy
  1545. # month    mm
  1546. # day    dd
  1547. # hour    hh
  1548. # min    mm
  1549. # The following are set in Date[] if given:
  1550. # tz    TTT|offset
  1551. # lyear    yyyy
  1552. # weekday Www
  1553. # sec    ss
  1554.  
  1555. # On success, the date in touch/date/etc. format (MMddhhmmyy) is returned.
  1556. # On failure, a negative value is returned.
  1557.  
  1558. # InDate form:
  1559. # [Weekday[,] (Month [d]d)|([d]d Month) TZ|time|year TZ|time|year [TZ|time|year]
  1560. # where Www is a weekday name that starts with a recognized 3-char prefix,
  1561. # Month is a month name that starts with a recognized 3-char prefix,
  1562. # [d]d is a day of the month,
  1563. # TZ is a timezone name (three upper case alpha chars) or offset from GMT
  1564. # as [-+]NNNN, time is of the form [h]h:mm[:ss],
  1565. # and year is of the form [cc]nn where cc is >= 19.
  1566. # Common patterns for a particular date:
  1567. # [Tue[,]]    Jun    12    11:02:46    [BST|-0800]    [19]90
  1568. # [Tue[,]]    12    Jun    [19]90        11:02:46    [BST|-0800]
  1569. function ParseDate(InDate,Date,  El,MonthName,TimeEl,i,Months,Ind,Num) {
  1570.     if (!("Jan" in Month2Num)) {
  1571.     MkMonth2Num()
  1572.     split("month,day,hour,min,year",TouchParts,",")
  1573.     }
  1574.     # Clear Date[]
  1575.     split("",Date," ")
  1576.     Num = split(InDate,El," +")
  1577.     if (!(4 <= Num && Num <= 6))
  1578.     return -1
  1579.     Ind = 1
  1580.     if (El[Ind] ~ "^(Mon|Tue|Wed|Thu|Fri|Sat|Sun)[a-z]*,?") {
  1581.     Date["weekday"] = substr(El[Ind],1,3)
  1582.     Ind++
  1583.     }
  1584.     if ((El[Ind] + 0) > 0) {
  1585.     Date["day"] = sprintf("%02d",El[Ind++])
  1586.     MonthName = substr(El[Ind++],1,3)
  1587.     }
  1588.     else {
  1589.     MonthName = substr(El[Ind++],1,3)
  1590.     Date["day"] = sprintf("%02d",El[Ind++])
  1591.     }
  1592.     if ((Date["day"] + 0 > 31) || (Date["day"] + 0 < 1))
  1593.     return -5
  1594.     if (!(MonthName in Month2Num))
  1595.     return -3
  1596.     Date["month"] = Month2Num[MonthName]
  1597.     for (; Ind <= Num; Ind++) {
  1598.     if ((El[Ind] ~ "^[0-2]?[0-9]:[0-5][0-9](:[0-5][0-9])?$") && \
  1599.     !("hour" in Date)) {
  1600.         split(El[Ind],TimeEl,":")
  1601.         Date["hour"] = sprintf("%02d",TimeEl[1])
  1602.         Date["min"] =  TimeEl[2]
  1603.         if (3 in TimeEl)
  1604.         Date["sec"] =  TimeEl[3]
  1605.     }
  1606.     else if ((El[Ind] ~ "^[1-9][0-9][0-9][0-9]$") && !("year" in Date)) {
  1607.         Date["year"] = substr(El[Ind],3)
  1608.         Date["lyear"] = El[Ind]
  1609.     }
  1610.     else if ((El[Ind] ~ "^[0-9][0-9]$") && !("year" in Date))
  1611.         Date["year"] = El[Ind]
  1612.     else if ((El[Ind] ~ "^([A-Z][A-Z][A-Z])|([-+][0-2][0-9][0-5][0-9])$") \
  1613.     && !("tz" in Date))
  1614.         Date["tz"] = El[Ind]
  1615.     else
  1616.         return -2
  1617.     }
  1618.     for (i in TouchParts)
  1619.     if (!(TouchParts[i] in Date))
  1620.         return -i - 5
  1621.     touchdate = Date["month"] Date["day"] Date["hour"] Date["min"] Date["year"]
  1622.     return touchdate
  1623. }
  1624.  
  1625. # Convert a file timestamp as printed by 'l' to an epoch time.
  1626. # noTZ should be 1 if the date output was produced with a 0 TZ; in this case
  1627. # no attempt is made to undo the TZ adjustment.
  1628. function lDate2unixtime(Mon,Day,Year,noTZ,  Month) {
  1629.     if (!(1 in Month2Num)) {
  1630.     MkMonth2Num()
  1631.     CurYear = strftime("%y")
  1632.     CurMonth = strftime("%m")
  1633.     }
  1634.     Month = Month2Num[Mon]
  1635.     # Deal with varying dates printed by l
  1636.     # Use year if given
  1637.     # Subtract 1 from year if month given is from last year
  1638.     if (Year ~ ":")    # If year is actually time...
  1639.     Year = (CurYear - (Month > CurMonth)) % 100
  1640.     return date2unixtime(Year,Month,Day,noTZ)
  1641. }
  1642.  
  1643. # Returns the number of seconds that passed from 1970 Jan 1 00:00:00
  1644. # to the given date.
  1645. # Timezone should be given as a numeric offset from GMT in seconds.
  1646. # Use 0 for Timezone if the date being converted was not generated with a
  1647. # timezone adjustment.
  1648. function unixtime(Year,Month,Day,Hour,Minute,Second,Timezone) {
  1649.     return ((YMD2day(Year,Month,Day) * 24 + Hour) * 60 + Minute) * 60 + \
  1650.     Second + Timezone
  1651. }
  1652.  
  1653. # date2unixtime returns the number of seconds that passed from 
  1654. # 1970 Jan 1 00:00:00 GMT to the given date, which is assumed to be in the
  1655. # local timezone.  Note that if the given date occured in daylight savings
  1656. # time and the current time (which is used to calculate TZOffset) is not,
  1657. # or vice versa, this will be off by the DST shift.
  1658. # If noTZ is set, the date is taken to be in GMT and TZ modifications are not
  1659. # done.
  1660. # Globals: Sets/uses TZOffset and MDays[].
  1661. function date2unixtime(Year,Month,Day,noTZ,   LeapDays) {
  1662.     if (!noTZ && TZOffset == "")
  1663.     MakeTZOffset()
  1664.     if (Year > 100)
  1665.     Year -= 1900
  1666.     LeapDays = int((Year - 68) / 4)
  1667.     if (Month <= 2 && Year % 4 == 0)
  1668.     LeapDays -= 1
  1669.     if (!MDays[2])
  1670.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  1671.     return ((Year - 70) * 365 + MDays[Month + 0] + Day - 1 + LeapDays) \
  1672.     * 24 * 3600 - (noTZ ? 0 : TZOffset)
  1673. }
  1674.  
  1675. # Sets global TZOffset to the number of seconds that need to be substracted
  1676. # from the local date (without time of day) to give an epoch time.
  1677. # TZOffset can also be added to systime() before doing %86400 to get the
  1678. # current day number in the local timezone.
  1679. # Note that TZOffset is only correct if the given date is in the same DST
  1680. # phase as the current date.
  1681. # 95/03/26 Calculate TZOffset more accurately.
  1682. function MakeTZOffset(  t) {
  1683.     t = systime()
  1684.     TZOffset = strftime("%H",t)*3600+strftime("%M",t)*60+strftime("%S",t) - \
  1685.     t%86400
  1686.     if (strftime("%j",0) != "001")    # If TZ offset > 0
  1687.     TZOffset -= 24*3600
  1688. }
  1689.  
  1690. # Convert a numeric timezone to a number of seconds.
  1691. # Example: converts -0830 to -30600
  1692. function TZ2sec(NTimezone) {
  1693.     if (NTimezone < 0) {
  1694.     NTimezone = substr(NTimezone,2)
  1695.     Mult = -1
  1696.     }
  1697.     else
  1698.     Mult = 1
  1699.     return (substr(NTimezone,1,2)*3600+substr(NTimezone,3,2)*60)*Mult
  1700. }
  1701.  
  1702. # Only works for current time... does *not* take a systime argument!
  1703. function my_strftime(Format,  Time) {
  1704.     "date \"+" Format "\"" | getline Time
  1705.     return Time
  1706. }
  1707.  
  1708. ### End timedate routines
  1709. ### Begin epochdays routines.
  1710. # These functions operate on epoch days and epoch months, which have the same 0
  1711. # time as UNIX epoch seconds.  These functions are mainly used to avoid having
  1712. # to deal with timezone issues.
  1713. # @(#) epochdays 1.1 95/08/26
  1714.  
  1715. # YMD2day(year,month,day-of-month) returns the number of days that passed from 
  1716. # 1970 Jan 1 to the given date.
  1717. # All parameters should be given in numeric form.
  1718. # If year < 70, it is assumed to be part of the 2000 century
  1719. # If year in (70..99), it is assumed to be part of the 1900 century.
  1720. # Globals: sets and uses MDays[]
  1721. function YMD2day(Year,Month,Day,   LeapDays) {
  1722.     Year+=0
  1723.     Month+=0
  1724.     if (Year < 70)
  1725.     Year += 100
  1726.     else if (Year >= 100)
  1727.     Year -= 1900
  1728.     # Year is now the number of years since 1900.
  1729.     LeapDays = int((Year - 68) / 4)
  1730.     if (Month <= 2 && Year % 4 == 0)
  1731.     LeapDays -= 1
  1732.     if (!(0 in MDays))
  1733.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  1734.     return (Year - 70) * 365 + MDays[Month] + Day - 1 + LeapDays
  1735. }
  1736.  
  1737. # date2day("yy/mm/dd") returns the number of days that passed from 
  1738. # 1970 Jan 1 to the given date.  -1 is returned on error.
  1739. # The fields are returned in Fields: year in Fields[1], month in Fields[2],
  1740. # and day (if given) in Fields[3].
  1741. function date2day(Date,Fields,  Num,Year,Month) {
  1742.     Num = split(Date,Fields,"/")
  1743.     if (Num != 2 && Num != 3)
  1744.     return -1
  1745.     if (!(Year = Fields[1] + 0) || !(Month = Fields[2] + 0))
  1746.     return -1
  1747.     if (Num == 3)
  1748.     Day = Fields[3]
  1749.     return YMD2day(Year,Month,Day)
  1750. }
  1751.  
  1752. # diffdays(year1,month1,day-of-month1,year2,month2,day-of-month2)
  1753. # returns the number of complete days that passed from date 1 to date 2
  1754. function diffdays(year1,month1,day1,year2,month2,day2) {
  1755.     return YMD2day(year2,month2,day2) - YMD2day(year1,month1,day1)
  1756. }
  1757.  
  1758. # Given an epoch month, return the first day of that month
  1759. function month2day(Month) {
  1760.     return YMD2day(int(Month/12) + 1970,Month % 12 + 1,1)
  1761. }
  1762.  
  1763. # Given an epoch day, returns epoch month
  1764. function day2month(Day,  Date) {
  1765.     day2YMD(Day,Date)
  1766.     return (Date["y"]-1970)*12 + Date["m"]-1
  1767. }
  1768.  
  1769. # Given an epoch month, returns the number of days in that month.
  1770. function monthdays(month,  year) {
  1771.     if (!(0 in MDur))
  1772.     split("31 28 31 30 31 30 31 31 30 31 30 31",MDur)
  1773.     year = int(month/12)
  1774.     month = month%12+1
  1775.     return (!((year+2)%4) && month == 2) ? 29 : MDur[month]
  1776. }
  1777.  
  1778. # Given an epoch day (day since 1970 Jan 1; day 0 = 1970 Jan 1, etc.), 
  1779. # returns the date elements in Date:
  1780. # Date["y"] = year (4 digits), Date["m"] = month (jan = 1, etc.),
  1781. # Date["d"] = day of month.
  1782. # Globals: Sets/uses MDays[].
  1783. function day2YMD(Day,Date,  QYears,Year,NonLeapYears,Month) {
  1784.     if (!(0 in LDays)) {
  1785.     split("0 31 59 90 120 151 181 212 243 273 304 334 365",MDays," ")
  1786.     split("0 31 60 91 121 152 182 213 244 274 305 335 366",LDays," ")
  1787.     }
  1788.     Day += 365
  1789.     # Day is now # of days since Jan 1 1969.  1968 was a leap year.
  1790.     QYears = int(Day / (365*4+1))
  1791.     Year = 1969 + QYears * 4
  1792.     Day -= QYears * (365*4+1)
  1793.     # Day now contains no complete leap years.
  1794.     Year += NonLeapYears = int(Day/365)
  1795.     Leap = !(Year % 4)
  1796.     Day -= NonLeapYears * 365
  1797.     # Day now contains the day of year.
  1798.     # Find the month.  Divide day by 32 to get either the correct month or
  1799.     # the month prior to it.
  1800.     Month = int(Day++ / 32) + 1
  1801.     if (Day > (Leap ? LDays[Month+1] : MDays[Month+1]))
  1802.     Month++
  1803.     Day -= Leap ? LDays[Month] : MDays[Month]
  1804.     Date["d"] = Day
  1805.     Date["m"] = Month
  1806.     Date["y"] = Year
  1807. }
  1808.  
  1809. # Given a month number, return a date in the form yy/mm
  1810. function month2date(MonthNum) {
  1811.     return sprintf("%02d/%02d",(MonthNum / 12 + 70) % 100, MonthNum % 12 + 1)
  1812. }
  1813.  
  1814. # Given a day number, return a date in the form yy/mm/dd
  1815. function day2date(day) {
  1816.     day2YMD(day,Date)
  1817.     return sprintf("%02d/%02d/%02d",Date["y"]%100,Date["m"],Date["d"])
  1818. }
  1819.  
  1820. ### End epochdays routines
  1821. ### Begin qsort routines
  1822.  
  1823. # Arr[] is an array of values with arbitrary indices.
  1824. # k[] is returned with numeric indices 1..n.
  1825. # The values in k[] are the indices of Arr[],
  1826. # ordered so that if Arr[] is stepped through
  1827. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1828. # through in order of the values of its elements.
  1829. # The return value is the number of elements in the arrays (n).
  1830. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1831.     ElNum = 0
  1832.     for (ArrInd in Arr)
  1833.     k[++ElNum] = ArrInd
  1834.     qsortSegment(Arr,k,1,ElNum)
  1835.     return ElNum
  1836. }
  1837.  
  1838. # Sort a segment of an array.
  1839. # Arr[] contains data with arbitrary indices.
  1840. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1841. # This function sorts the elements of arr that are pointed to by
  1842. # k[start..end], swapping the values of elements of k[] so that
  1843. # when this function returns arr[k[start..end]] will be in order.
  1844. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1845.     # handle two-element case explicitly for a tiny speedup
  1846.     if ((end - start) == 1) {
  1847.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1848.         k[start] = tmpe
  1849.         k[end] = tmps
  1850.     }
  1851.     return
  1852.     }
  1853.     # Make sure comparisons act on these as numbers
  1854.     left = start+0
  1855.     right = end+0
  1856.     sepval = Arr[k[int((left + right) / 2)]]
  1857.     # Make every element <= sepval be to the left of every element > sepval
  1858.     while (left < right) {
  1859.     while (Arr[k[left]] < sepval)
  1860.         left++
  1861.     while (Arr[k[right]] > sepval)
  1862.         right--
  1863.     if (left < right) {
  1864.         tmp = k[left]
  1865.         k[left++] = k[right]
  1866.         k[right--] = tmp
  1867.     }
  1868.     }
  1869.     if (left == right)
  1870.     if (Arr[k[left]] < sepval)
  1871.         left++
  1872.     else
  1873.         right--
  1874.     if (start < right)
  1875.     qsortSegment(Arr,k,start,right)
  1876.     if (left < end)
  1877.     qsortSegment(Arr,k,left,end)
  1878. }
  1879.  
  1880. # Arr[] is an array of values with arbitrary indices.
  1881. # k[] is returned with numeric indices 1..n.
  1882. # The values in k are the indices of Arr[],
  1883. # ordered so that if Arr[] is stepped through
  1884. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1885. # through in order of the values of its indices.
  1886. # The return value is the number of elements in the arrays (n).
  1887. # If the indexes are numeric, Numeric should be true, so that they can be
  1888. # compared as such rather than as strings.  Numeric indexes do not have to be
  1889. # contiguous.
  1890. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1891.     ElNum = 0
  1892.     if (Numeric)
  1893.     # Indexes do not preserve numeric type, so must be forced
  1894.     for (ArrInd in Arr)
  1895.         k[++ElNum] = ArrInd+0
  1896.     else
  1897.     for (ArrInd in Arr)
  1898.         k[++ElNum] = ArrInd
  1899.     qsortNumIndByValue(k,1,ElNum)
  1900.     return ElNum
  1901. }
  1902.  
  1903. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1904. # by value.
  1905. # start and end are the starting and ending indexes of the range to be sorted.
  1906. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1907.     # handle two-element case explicitly for a tiny speedup
  1908.     if ((start - end) == 1) {
  1909.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1910.         Arr[start] = tmpe
  1911.         Arr[end] = tmps
  1912.     }
  1913.     return
  1914.     }
  1915.     left = start+0
  1916.     right = end+0
  1917.     sepval = Arr[int((left + right) / 2)]
  1918.     while (left < right) {
  1919.     while (Arr[left] < sepval)
  1920.         left++
  1921.     while (Arr[right] > sepval)
  1922.         right--
  1923.     if (left <= right) {
  1924.         tmp = Arr[left]
  1925.         Arr[left++] = Arr[right]
  1926.         Arr[right--] = tmp
  1927.     }
  1928.     }
  1929.     if (start < right)
  1930.     qsortNumIndByValue(Arr,start,right)
  1931.     if (left < end)
  1932.     qsortNumIndByValue(Arr,left,end)
  1933. }
  1934.  
  1935. ### End qsort routines
  1936.